﻿using System;
using System.Collections.Generic;
using System.Linq;
using CQRS.Events.Memento;

namespace CQRS.Events
{
    public class DomainRepository<TEvent> : IDomainRepository<TEvent> where TEvent : Event
    {
        private readonly IBus<Event> _bus;
        private readonly IEventRepository<TEvent> _domainEventRepository;
        private readonly List<IEventProvider<TEvent>> _eventProviders;

        public DomainRepository(IEventRepository<TEvent> domainEventRepository, IBus<Event> bus)
        {
            _domainEventRepository = domainEventRepository;
            _bus = bus;
            _eventProviders = new List<IEventProvider<TEvent>>();
        }

        #region IDomainRepository<TEvent> Members

        public TAggregate GetById<TAggregate>(Guid id)
            where TAggregate : class, IOriginator, IEventProvider<TEvent>, new()
        {
            var aggregateRoot = new TAggregate();

            LoadSnapshotIfExists(id, aggregateRoot);

            LoadRemainingHistoryEvents(id, aggregateRoot);

            return aggregateRoot;
        }

        public void Add<TAggregate>(TAggregate aggregateRoot)
            where TAggregate : class, IOriginator, IEventProvider<TEvent>, new()
        {
            _eventProviders.Add(aggregateRoot);
        }

        public void Commit()
        {
            foreach (var eventProvider in _eventProviders)
            {
                _domainEventRepository.Save(eventProvider);
                _bus.Publish(eventProvider.GetChanges());
                eventProvider.Clear();
            }
            _eventProviders.Clear();

            _bus.Commit();
        }

        #endregion

        public void Rollback()
        {
            _bus.Rollback();
            _eventProviders.Clear();
        }

        private void LoadSnapshotIfExists(Guid id, IOriginator aggregateRoot)
        {
            ISnapshot snapshot = _domainEventRepository.GetSnapshot(id);
            if (snapshot == null)
                return;

            aggregateRoot.SetMemento(snapshot.Memento);
        }

        private void LoadRemainingHistoryEvents(Guid id, IEventProvider<TEvent> aggregateRoot)
        {
            List<TEvent> events = _domainEventRepository.GetEventsSinceLastSnapshot(id).ToList();
            if (events.Count() > 0)
            {
                aggregateRoot.LoadFromHistory(events);
                return;
            }

            aggregateRoot.LoadFromHistory(_domainEventRepository.GetAllEvents(id).ToList());
        }
    }
}